/**
 @file drv_io.c

 @date 2010-02-26

 @version v5.1

 The file contains all driver I/O interface realization
*/

#include "asw/include/drv_api.h"
#include "asw/include/drv_io.h"
#include "sal_mutex.h"

#define DRV_IOC_OP(cmd)      (((cmd) >> 29)&7)
#define DRV_IOC_MEMID(cmd)   (((cmd) >> 13)&0xFFFF)
#define DRV_IOC_FIELDID(cmd) (cmd &0x1FFF)
#define MAX_ENTRY_WORD  32
#define DRV_TABLE_TYPE_WITH_MASK 1


drv_master_t *p_asw_drv_master = NULL;
int32 dev_fd = -1;
/**
 @brief Get a field of a memory data entry
*/
int32
drv_asw_get_field(uint8 lchip, uint32 tbl_id, uint32 field_id,
                uint32* entry, uint32* value)
{
    const fields_t* field = NULL;
    const segs_t* seg = NULL;
    register uint16 start_bit = 0;
    register uint16 word_offset = 0;
    register int32  cut2_bits = 0;
    register int32  cut_bits = 0;
    uint8 loop = 0;
    uint8 loop_cnt = 0;
    uint8  left_bits = 0;
    field = TABLE_FIELD_INFO_PTR(lchip, tbl_id) + field_id;

    seg = &(field->seg);
    loop_cnt = field->bits>>5;
    word_offset = seg->start>>5;
    start_bit =  seg->start & 0x1F;
    cut_bits = (32 < start_bit+field->bits)? (32-start_bit): field->bits;
    cut2_bits = start_bit;

    while(loop < loop_cnt)
    {
        value[loop] = (entry[word_offset] >>cut2_bits) & SHIFT_LEFT_MINUS_ONE(cut_bits);
        cut2_bits =  32 - cut_bits;
        word_offset++;
        value[loop] |= (entry[word_offset]  &  SHIFT_LEFT_MINUS_ONE(cut2_bits))<<cut_bits;
        cut_bits = 32 - cut2_bits;
        loop++;
    }
    left_bits = field->bits - (loop<<5);

    if(left_bits)
    {/*last bits */
        value[loop] = (entry[word_offset] >>cut2_bits) & SHIFT_LEFT_MINUS_ONE(cut_bits);
        cut2_bits =  left_bits - cut_bits;
        if(left_bits > cut_bits)
        {
        word_offset++;
        value[loop] |= ((entry[word_offset]  &  SHIFT_LEFT_MINUS_ONE(cut2_bits))<<cut_bits);
        }
    }

    return DRV_E_NONE;
}

/**
 @brief Set a field of a memory data entry
*/
int32
drv_asw_set_field(uint8 lchip, uint32 tbl_id, uint32 field_id, uint32* entry, uint32 *value)
{
    const fields_t* field = NULL;
    const segs_t* seg = NULL;
    register uint16 start_bit = 0;
    register uint16 word_offset = 0;
    register int32  cut2_bits = 0;
    register int32  cut_bits = 0;
    uint8 loop = 0;
    uint8 loop_cnt = 0;
    uint8  left_bits = 0;
    field = TABLE_FIELD_INFO_PTR(lchip, tbl_id) + field_id;

    seg = &(field->seg);
    loop_cnt = field->bits>>5;
    word_offset = seg->start>>5;
    start_bit =  seg->start&0x1F;
    cut_bits = (32 < start_bit+field->bits)? (32-start_bit): field->bits;
    cut2_bits = start_bit;

    while(loop < loop_cnt)
    {
        entry[word_offset] &= ~(SHIFT_LEFT_MINUS_ONE(cut_bits) <<cut2_bits);
        entry[word_offset] |=  (value[loop] & SHIFT_LEFT_MINUS_ONE(cut_bits))<<cut2_bits;
        cut2_bits =  32 - cut_bits;

        word_offset++;
        if (cut2_bits)
        {
            entry[word_offset] &= ~ SHIFT_LEFT_MINUS_ONE(cut2_bits);
            entry[word_offset] |= (value[loop] >>cut_bits) &  SHIFT_LEFT_MINUS_ONE(cut2_bits);
            cut_bits = 32 - cut2_bits;
        }
        loop++;
    }
    left_bits = field->bits  - (loop<<5);
    cut_bits = (cut_bits>left_bits)?left_bits:cut_bits;
    if(left_bits)
    {/*last bits */
        entry[word_offset] &= ~(SHIFT_LEFT_MINUS_ONE(cut_bits) <<cut2_bits);
        entry[word_offset] |=  (value[loop] & SHIFT_LEFT_MINUS_ONE(cut_bits))<<cut2_bits;
        cut2_bits =  left_bits - cut_bits;

        if(left_bits > cut_bits )
        {
        word_offset++;
        entry[word_offset] &= ~ SHIFT_LEFT_MINUS_ONE(cut2_bits);
        entry[word_offset] |= (value[loop] >>cut_bits) &  SHIFT_LEFT_MINUS_ONE(cut2_bits);
        }
    }
    return DRV_E_NONE;
}

int32
drv_asw_set_field32(uint8 lchip, uint32 tbl_id, uint32 field_id, uint32* entry, uint32 value)
{
    const fields_t* field = NULL;
    const segs_t* seg = NULL;
    register uint16 start_bit = 0;
    register uint16 word_offset = 0;
    register int32  left_bits = 0;
    register int32  cut_bits = 0;
    field = TABLE_FIELD_INFO_PTR(lchip, tbl_id) + field_id;

    seg = &(field->seg);
    word_offset = seg->start >> 5;
    start_bit =  seg->start & 0x1F;
    left_bits  = start_bit +field->bits -32 ;
    cut_bits = (32 < start_bit+field->bits)? (32-start_bit): field->bits;

    entry[word_offset] &= ~(SHIFT_LEFT_MINUS_ONE(cut_bits)  <<start_bit);
    entry[word_offset] |= ((value & SHIFT_LEFT_MINUS_ONE(cut_bits)) << start_bit);

    if(left_bits > 0)
    {
        entry[word_offset+1] &= ~ SHIFT_LEFT_MINUS_ONE(left_bits) ;
        entry[word_offset+1] |= (value >> cut_bits) & SHIFT_LEFT_MINUS_ONE(left_bits);
    }
    return DRV_E_NONE;
}

uint32
drv_asw_get_field32(uint8 lchip, uint32 tbl_id, uint32 field_id, uint32* entry)
{
    const fields_t* field = NULL;
    const segs_t* seg = NULL;
    register uint16 start_bit = 0;
    register uint16 word_offset = 0;
    register int32  left_bits = 0;
    register int32  cut_bits = 0;
    register  uint32 value;
    field = TABLE_FIELD_INFO_PTR(lchip, tbl_id) + field_id;

    seg = &(field->seg);
    word_offset = seg->start >> 5;
    start_bit =  seg->start & 0x1fu;
    left_bits  = start_bit +field->bits -32;
    cut_bits = (32 < start_bit+field->bits)? (32-start_bit): field->bits;

    value = (entry[word_offset] >> start_bit) & SHIFT_LEFT_MINUS_ONE(cut_bits);
    if(left_bits > 0)
    {
        value |= (entry[word_offset+1]  &  SHIFT_LEFT_MINUS_ONE(left_bits))<<cut_bits;
    }
    return value;
}

int32
drv_asw_tbl_ioctl_null(uint8 lchip, uint32 tbl_id, uint32 index, uint32* data)
{
   return DRV_E_INVALID_TBL;
}
/**
 @brief The function is the table I/O control API
*/
int32
drv_asw_tbl_ioctl(uint8 lchip, int32 index, uint32 cmd, void* val)
{
    uint16 action;
    uint32 tbl_id;
    const tables_info_t *ptbl;

    DRV_PTR_VALID_CHECK(val);

    action = DRV_IOC_OP(cmd);
    tbl_id = DRV_IOC_MEMID(cmd);
    DRV_TBL_VALID_CHECK(tbl_id);

    ptbl = &drv_tbls_list[tbl_id];
    if (index >= ptbl->entry_num ||action > DRV_IOC_HASH_DUMP )
    {
        DRV_DBG_INFO("\nERROR (drv_write_sram_tbl): chip-0x%x, tbl-0x%x, index-0x%x exceeds the max_index-0x%x.\n", lchip, tbl_id, index, ptbl->entry_num);
        return DRV_E_INVALID_TBL;
    }
    return  p_asw_drv_master->ioctl_cb[action](lchip, tbl_id, index, val);
}
int32
drv_asw_tbl_field_ioctl(uint8 lchip, int32 index, uint32 cmd, void* val)
{
    uint16 action;
    uint32 tbl_id;
    uint16 field_id;
    const tables_info_t *ptbl;
    int32 ret = 0;
    uint32  data_entry[MAX_ENTRY_WORD];

    DRV_PTR_VALID_CHECK(val);

    action = DRV_IOC_OP(cmd);
    tbl_id = DRV_IOC_MEMID(cmd);
    field_id = DRV_IOC_FIELDID(cmd);
    DRV_TBL_VALID_CHECK(tbl_id);

    ptbl = &drv_tbls_list[tbl_id];
    if (index >= ptbl->entry_num ||action > DRV_IOC_HASH_DUMP )
    {
        DRV_DBG_INFO("\nERROR (drv_write_sram_tbl): chip-0x%x, tbl-0x%x, index-0x%x exceeds the max_index-0x%x.\n", lchip, tbl_id, index, ptbl->entry_num);
        return DRV_E_INVALID_TBL;
    }
    if(action == DRV_IOC_WRITE)
    {
        ret = p_asw_drv_master->ioctl_cb[DRV_IOC_READ](lchip, tbl_id, index,data_entry);
        drv_asw_set_field(lchip, tbl_id, field_id, data_entry, (uint32*)val);
        ret = ret?ret: p_asw_drv_master->ioctl_cb[DRV_IOC_WRITE](lchip, tbl_id, index, data_entry);
    }
    else
    {
        ret = p_asw_drv_master->ioctl_cb[DRV_IOC_READ](lchip, tbl_id, index, data_entry);
        drv_asw_get_field(lchip, tbl_id, field_id, data_entry, (uint32*)val);
    }
    return ret;
}

int32
drv_asw_mask_tbl_ioctl(uint8 lchip, int32 index, uint32 cmd, void* val)
{
    int32 action;
    uint32 tbl_id;
    const tables_info_t *ptbl;

    DRV_PTR_VALID_CHECK(val);
    action = DRV_IOC_OP(cmd);
    tbl_id = DRV_IOC_MEMID(cmd);

    DRV_TBL_VALID_CHECK(tbl_id);

    ptbl = &drv_tbls_list[tbl_id];
    if (ptbl->tbl_type  != TBL_SRAM_MASK || index >= ptbl->entry_num || action > DRV_IOC_HASH_DUMP )
    {
        DRV_DBG_INFO("\nERROR (drv_write_sram_tbl): chip-0x%x, tbl-0x%x, index-0x%x exceeds the max_index-0x%x.\n", lchip, tbl_id, index, ptbl->entry_num);
        return DRV_E_INVALID_TBL;
    }
    return  p_asw_drv_master->ioctl_mask_cb[action](lchip, tbl_id, index,(uint32*)val);
}

int32 drv_asw_init(uint8 lchip)
{
#ifdef MEM_MODEL
    uint16 loop = 0;
    const tables_info_t *ptbl;
    uint8 idx = 0;
#endif
    int32 ret = 0;

    if(lchip > DRV_MAX_CHIP_NUM)
    {
        return DRV_E_INVALID_CHIP;
    }
    if (NULL == p_asw_drv_master)
    {
        p_asw_drv_master = sal_malloc(sizeof(drv_master_t));
        sal_memset(p_asw_drv_master, 0, sizeof(drv_master_t));
    }

    sal_mutex_create(&p_asw_drv_master->drv_mutex[lchip]);
    if (NULL == p_asw_drv_master->drv_mutex[lchip])
    {
        ret = DRV_E_NOT_INIT;
        goto roll_back;
    }

    if (0 != p_asw_drv_master->valid_num++)
    {
        return DRV_E_NONE;
    }
#ifdef MEM_MODEL
    p_asw_drv_master->model_en = 1;
    for(loop = 0;loop  < MaxTblId_t;loop++)
    {
        ptbl = &drv_tbls_list[loop];
        for (idx=0; idx<TABLE_ADDR_NUM(lchip, loop); idx++)
        {
            ptbl->addrs[idx] = (uintptr)sal_malloc(ptbl->entry_num/TABLE_ADDR_NUM(lchip, loop) * ptbl->entry_offset);
        }
    }
#else
    p_asw_drv_master->model_en = 0;
#endif

    p_asw_drv_master->ioctl_cb[DRV_IOC_READ] = drv_asw_chip_sram_tbl_read;
    p_asw_drv_master->ioctl_cb[DRV_IOC_WRITE] = drv_asw_chip_sram_tbl_write;
    p_asw_drv_master->ioctl_cb[DRV_IOC_DELETE] = drv_asw_tbl_ioctl_null;
    p_asw_drv_master->ioctl_cb[DRV_IOC_HASH_ADD] = drv_asw_chip_hash_add;
    p_asw_drv_master->ioctl_cb[DRV_IOC_HASH_REMOVE] = drv_asw_chip_hash_delete;
    p_asw_drv_master->ioctl_cb[DRV_IOC_HASH_LOOKUP] = drv_asw_chip_hash_lookup;
    p_asw_drv_master->ioctl_cb[DRV_IOC_HASH_DUMP] = drv_asw_chip_hash_dump;

    p_asw_drv_master->ioctl_mask_cb[DRV_IOC_READ] = drv_asw_chip_mask_tbl_read;
    p_asw_drv_master->ioctl_mask_cb[DRV_IOC_WRITE] = drv_asw_chip_mask_tbl_write;
    p_asw_drv_master->ioctl_mask_cb[DRV_IOC_DELETE] = drv_asw_chip_mask_tbl_delete;
    p_asw_drv_master->ioctl_mask_cb[DRV_IOC_HASH_ADD] = drv_asw_tbl_ioctl_null;
    p_asw_drv_master->ioctl_mask_cb[DRV_IOC_HASH_REMOVE] = drv_asw_tbl_ioctl_null;
    p_asw_drv_master->ioctl_mask_cb[DRV_IOC_HASH_LOOKUP] = drv_asw_tbl_ioctl_null;
    p_asw_drv_master->ioctl_mask_cb[DRV_IOC_HASH_DUMP] = drv_asw_tbl_ioctl_null;

    return 0;
roll_back:
    if (0 == p_asw_drv_master->valid_num)
    {
        sal_free(p_asw_drv_master);
    }
    return ret;
}

void
drv_asw_set_io_wr_dis(uint8 lchip, uint32 value)
{
    p_asw_drv_master->io_wr_dis[lchip] = value ? 1 : 0;
}

void 
drv_asw_get_io_wr_dis(uint8 lchip, uint32 * p_value)
{
    *p_value = p_asw_drv_master->io_wr_dis[lchip] ? 1 : 0;
}

int32
drv_asw_deinit(uint8 lchip)
{
    if (NULL == p_asw_drv_master)
    {
        return DRV_E_NOT_INIT;
    }
    sal_mutex_destroy(p_asw_drv_master->drv_mutex[lchip]);
    p_asw_drv_master->valid_num--;
    if (0 == p_asw_drv_master->valid_num)
    {
        sal_free(p_asw_drv_master);
    }
    return 0;
}
